/**
 * Copyright (c) 2007, Blue Hole Software. All rights reserved.
 * Code licensed under the Apache 2.0 License:
 * http://www.apache.org/licenses/
 */
package org.bhf.i18n;

import java.io.Serializable;
import java.text.MessageFormat;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

/**
 * <p>
 * A displayable message such as an error or informational notice. The message text is expected to be valid
 * HTML. If you are pulling messages from a localization bundle, the messages must have already been escaped
 * in the localization bundle. If you are using a prelocalized message, you can escape the message using
 * <code>Message.escape(...)</code>
 * </p><p>
 * Note thet a <code>ContextLocation</code> must have been established in order to localize a <code>Message</code>.
 * </p>
 * @see org.bhf.i18n.ContextLocation
 */
public final class Message implements Serializable
{
    private static final long serialVersionUID = -8218781674224651040L;

    private final String            topic;
    private final String            summary;
    private final String            detail;
    private final String            bundle;
    private final MessageType       type;
    private final Object[]          parameters;

    /**
     * Escape the specified <code>String</code> for characters that are senstive to
     * HTML interpreters, returning the <code>String</code> with these characters replaced
     * by the corresponding character entities.
     *
     * @param value The string to be filtered and returned.
     *
     * @return The escaped <code>String</code>.
     */
    public static String escape( String value )
    {

        if( value == null )
        {
            return (null);
        }

        char[] content = new char[value.length()];
        value.getChars( 0, value.length(), content, 0 );
        StringBuffer result = new StringBuffer( content.length + 50 );

        for( char c : content )
        {
            switch( c )
            {
                case '<':
                    result.append("&lt;");
                    break;
                case '>':
                    result.append("&gt;");
                    break;
                case '&':
                    result.append("&amp;");
                    break;
                case '"':
                    result.append("&quot;");
                    break;
                case '\'':
                    result.append("&#39;");
                    break;
                default:
                    result.append( c );
            }
        }

        return result.toString();
    }

    /**
     * Verbose constructor. You should usually use the factory methods on <code>MessageType</code> and
     * <code>ModuleInfo</code> rather than this ctor.
     *
     * @param topic If this message is associated with a property on the page, the property
     *  path, such as <code>foo.bar[3].name</code>. If this message is not associated with any
     *  property, the topic may be any other appropriate string. Must not be <code>null</code>.
     * @param summary Must not be <code>null</code>.
     * @param detail If <code>null</code>, defaults to the summary.
     * @param type A well known message type, such as <code>ERROR</code>.
     * @param bundle Resource bundle for localizing this message. If <code>null</code>, the message
     * @param parameters Message substitution parameters. May be <code>null</code>.
     *
     *  is assumed to have already been localized or, for some other reason, does not localization.
     */
    public Message(
        final String            topic,
        final String            summary,
        final String            detail,
        final MessageType       type,
        final String            bundle,
        final Object...         parameters
    )
    {
        assert topic != null;
        assert summary != null;
        assert type != null;

        this.topic          = topic;
        this.summary        = summary;
        this.detail         = detail != null ? detail : summary;
        this.parameters     = parameters;
        this.type           = type;
        this.bundle         = bundle;
    }

    /**
     * Localize this <code>Message</code> for display using the current contextual
     * <code>Locale</code>. Note that <code>ContextLocation</code> ust be set up for
     * this to work.
     * @return Localized <code>Message</code>.
     */
    public Message  localize()
    {
        if( bundle == null ) // Already localized, just need subs
        {
            String newSummary   = getSummary();
            String newDetail    = getDetail();

            if( newSummary != null )
            {
                newSummary = MessageFormat.format( newSummary, parameters );
            }
            if( newDetail != null )
            {
                newDetail = MessageFormat.format( newDetail, parameters );
            }

            return new Message( topic, newSummary, newDetail, type, null );
        }
        else
        {
            // TODO Cache bundles, use this code in MessageTag
            try
            {
                final ResourceBundle resourceBundle = ResourceBundle.getBundle(
                    bundle,
                    ContextLocation.getCurrentContextLocation().getLocale(),
                    Thread.currentThread().getContextClassLoader()
                );

                String newSummary   = getSummary();
                String newDetail    = getDetail();

                if( newSummary != null )
                {
                    newSummary = MessageFormat.format( resourceBundle.getString( newSummary ), parameters );
                }
                if( newDetail != null )
                {
                    newDetail = MessageFormat.format( resourceBundle.getString( newDetail ), parameters );
                }

                return new Message( topic, newSummary, newDetail, type, null );
            }
            catch( MissingResourceException ignored )
            {
                return this;
            }
        }
    }

    /**
     * Prepend a prefix to the <code>Message</code> topic.
     * @param topicPrefix Topic prefix (does not include the '.' if needed)
     * @return Modified <code>Message</code>
     */
    public Message          prependToTopic( final String topicPrefix )
    {
        return new Message( topicPrefix + topic, summary, detail, type, bundle, parameters );
    }

    /**
     * <code>true</code> iff this message is an error.
     * @return <code>true</code> iff this message is an error.
     */
    public boolean          isError()
    {
        return type == MessageType.ERROR;
    }

    /**
     * <code>true</code> iff this message is informational.
     * @return <code>true</code> iff this message is informational.
     */
    public boolean          isInfo()
    {
        return type == MessageType.INFO;
    }

    /**
     * <code>true</code> iff this message is for debuging.
     * @return <code>true</code> iff this message is for debugging.
     */
    public boolean          isDebug()
    {
        return type == MessageType.DEBUG;
    }

    /**
     * <code>true</code> if this message's summary and detail should be taken verbatim and not localized.
     * @return <code>true</code> if this message's summary and detail should be taken verbatim and not localized.
     */
    public boolean          isLocalized()
    {
        return bundle == null;
    }

    /**
     * By convention the property path to the service property with which this message is associated, or some
     * other appropriate topic.
     * @return Will not be <code>null</code>
     */
    public String           getTopic()
    {
        return topic;
    }

    /**
     * Returns the summary.
     *
     * @return the summary.  Will not be <code>null</code>
     */
    public String           getSummary()
    {
        return summary;
    }

    /**
     * Returns the detail.
     *
     * @return The detail.  Will not be <code>null</code>
     */
    public String           getDetail()
    {
        return detail;
    }

    /**
     * A well known type such as <code>ERROR</code>.
     * @return Will not be <code>null</code>
     */
    public MessageType      getType()
    {
        return type;
    }

    /**
     * Return message substitution parameters. Applies to both the summary and detail messages.
     * @return The message substitution parameters. May be <code>null</code>.
     */
    public Object[]         getParameters()
    {
        return parameters;
    }
}